# miaoma/models/table_info.py
#
# 本模块用于抽象描述数据库表的结构信息，并将其封装为数据类 (dataclass) 形式，
# 以便在不同编程语言和框架中统一使用和管理这些元数据信息。
#
# TableInfoSQL 类为代码生成提供了基础信息。它包含数据库表的表名、实体类名、包名、字段信息等，
# 这些信息可以被用于自动生成多种编程语言的代码（如 Java 实体类、SpringBoot 代码、React 组件等），
# 从而减少手动编码的工作量和错误概率，提高开发效率。
#
# 该模块的主要功能是通过解析 SQL 表格的 CREATE TABLE 语句，将 SQL 表的元数据信息转换为
# TableInfoSQL 类的实例。这些信息可以被广泛应用于不同语言和框架的代码生成流程中，包括：
#
# 1. **SQL 解析与元数据抽象**
#    - 当前流程：解析 SQL 的 CREATE TABLE 语句，扫描数据库中的表结构信息。
#    - 将解析结果封装为 TableInfoSQL 类的实例，抽象出表名、字段信息、主键、唯一性约束等关键信息。
#    - 将数据库表的结构信息以编程方式进行标准化处理，便于后续操作。
#
# 2. **未来流程：字段元信息定义与 SQL 生成**
#    - 将从定义字段元信息的方式开始，例如 ColumnInfoSQL 实例，描述字段名称、类型、长度、主键、外键、索引等信息。
#    - 根据定义的字段元信息自动生成对应的 SQL 语句（如 CREATE TABLE 语句），确保数据库结构与代码模型一致。
#    - 通过这种方式，不再依赖已有的数据库结构，可以从代码层面统一定义表结构。
#
# 3. **代码生成的基础信息**
#    - TableInfoSQL 实例作为基础信息，当前可用于生成多种编程语言的代码：
#      - **后端代码**：如 Java 实体类、Spring Data JPA 仓库接口、MyBatis 映射文件等。
#      - **前端代码**：如 React 表单组件、TypeScript 类型声明、Vue 组件等。
#    - 未来可基于字段元信息类（如 ColumnInfoSQL）映射转化为不同语言或框架的元信息类：
#      - **SpringBootMetaInfo**：适用于 SpringBoot 框架的 Java 类和 DAO 层代码生成。
#      - **ReactMetaInfo**：适用于 React 的表单组件、类型声明等前端代码生成。
#      - **GinMetaInfo**：适用于 Go 语言 Gin 框架的实体类和数据库操作代码生成。
#      - **VueMetaInfo**：适用于 Vue 框架的表单组件和前端代码生成。
#    - 这种方式可以自动生成符合业务需求的多种代码文件，提高开发效率并保持代码一致性。
#
# 4. **模板引擎与自动化生成**
#    - 将 TableInfoSQL 或各类元信息映射类结合 jinja2 模板引擎生成代码文件。
#    - 根据字段元信息类的定义，可以自动生成不同语言和框架的代码文件，并支持多种业务逻辑。
#    - 新的流程中，可先定义字段元信息，再生成 SQL 语句和代码，从而更灵活地适配不同系统需求。
#
# 5. **提升开发效率和一致性**
#    - 通过标准化表结构和字段元信息的定义，可以避免在不同系统中手动维护同样的表结构信息。
#    - 自动生成的代码文件不仅减少了手动编码的工作量，还降低了出错概率，提升了开发效率和代码一致性。
#    - 未来支持从字段元信息生成 SQL 和代码文件，将进一步增强开发过程中的灵活性和一致性。
#
# 总结来说，TableInfoSQL 类将 SQL 表格的元数据信息抽象为一个数据结构，
# 可以在程序中以结构化和标准化的方式进行操作和管理，为自动化代码生成提供了统一的数据基础。
# 随着未来流程的调整，将支持从字段元信息到 SQL 和代码的全流程自动化生成，进一步提高开发效率和灵活性。

from typing import List, Optional, Dict
from enum import Enum
from dataclasses import dataclass
from openpyxl import Workbook, load_workbook
from openpyxl.worksheet.datavalidation import DataValidation
import pandas as pd
from typing import List
import os

# 自动生成的数据库字段信息
@dataclass
class ColumnInfoSQL:
    name: str                          # 字段名称
    type: str                          # 字段的sql类型
    max_length: Optional[int] = None   # 字段的最大长度，例如 VARCHAR(100) 中的 100
    primary_key: bool = False          # 是否为主键
    auto_increment: bool = False       # 是否自动递增
    nullable: bool = True              # 是否允许为空，默认允许
    default_value: Optional[str] = None  # 默认值
    unique: bool = False               # 是否唯一
    foreign_key: Optional[str] = None  # 外键引用，例如 "other_table.other_column"
    index_type: Optional[str] = None   # 索引类型，例如 "INDEX", "UNIQUE INDEX"
    comment: Optional[str] = None      # 字段的注释

# 自动生成的数据库表信息
@dataclass
class TableInfoSQL:
    table_name: str                   # 表名(通过扫码SQL语句自动生成的表名)
    columns: List[ColumnInfoSQL]      # 字段信息列表
    primary_key_columns: List[str] = None       # 主键字段列表
    unique_constraints: List[List[str]] = None  # 唯一约束列表，例如 [["email"], ["username", "created_at"]]
    table_comment: Optional[str] = None         # 表的注释

# 定义字段类型的枚举类
class FieldType(Enum):
    TEXT = "text"           # 文本类型，例如输入框
    NUMBER = "number"       # 数字类型，例如数字输入框
    IMAGE = "image"         # 图片类型，例如数字输入框
    DATE = "date"           # 日期类型，例如日期选择器
    DATETIME = "datetime"   # 日期类型，例如日期选择器
    COLOR = "color"         # 颜色类型，例如颜色选择器
    BOOLEAN = "boolean"     # 布尔类型，例如开关按钮或复选框
    SELECT = "select"       # 下拉选择类型，配合值映射关系使用，例如下拉菜单选项

    @classmethod
    def from_str(cls, value: str):
        # 将输入字符串转换为对应的枚举值，如果不匹配则返回 TEXT
        value = value.lower()  # 转换为小写以适配大小写不一致的情况
        if value in cls._value2member_map_:
            return cls._value2member_map_[value]
        else:
            return cls.TEXT

# 手动配置的字段信息
@dataclass
class ColumnConfig:
    name: str                        # 数据库中的字段名称（方便和ColumnInfoSQL映射关联）
    chinese_name: str=''             # 字段的中文名
    column_show: bool = True         # 是否在表格中显示该列
    nullable: bool = False           # 是否允许为空
    editable: bool = True            # 是否可编辑
    comment: str = '默认备注'         # 字段备注
    field_type: FieldType = FieldType.TEXT  # 字段类型，默认是 text 类型
    value_map: Dict[int, str] = None        # 值映射关系（可选）
    mock_rule: str = ''                     # 模拟数据规则（可选）

    def __repr__(self):
        # 自定义 ColumnConfig 的字符串表示形式
        show_flag = "显示" if self.column_show else "隐藏"
        nullable_flag = "可为空" if self.nullable else "不可为空"
        editable_flag = "可编辑" if self.editable else "不可编辑"
        return (f"ColumnConfig(中文名: {self.chinese_name}, 字段名称: {self.name}, 是否显示: {show_flag}, "
                f"是否允许为空: {nullable_flag}, 是否可编辑: {editable_flag}), "
                f"值映射: {self.value_map}, 字段类型: {self.field_type.value}, 模拟规则: {self.mock_rule}")

# 人工配置的表格信息（更适合生成模板文件）
@dataclass
class TableInfoConfig:
    chinese_name: str                 # 表的中文名
    table_name: str                   # 数据库表的全名，如 "t_sys_user", "t_sys_user_auth"
    entity_name: str                  # 小写形式的表名称，如 "user", "userauth"
    class_name: str                   # 驼峰命名法的类名，用于 Java 类、React 组件等，例如 User, UserAuth
    java_package_name: str            # Java 包名，如 "com.example.miaoma"
    current_date: str                 # 当前日期，如 "2024-09-25"
    version: str                      # 代码生成器的版本信息，如 "miaoma generator v1.0.0"
    skip: bool                        # 跳过标志
    columns: List[ColumnConfig]
    target_language: str = 'java'     # 目标语言，默认为 'java'

    def __repr__(self):
        # 自定义 TableInfoConfig 的字符串表示形式
        columns_str = "\n    ".join(repr(col) for col in self.columns)
        return (
            f"TableInfoConfig(\n"
            f"  表中文名: {self.chinese_name},\n"
            f"  表名: {self.table_name},\n"
            f"  实体名: {self.entity_name},\n"
            f"  类名: {self.class_name},\n"
            f"  包名: {self.java_package_name},\n"
            f"  当前日期: {self.current_date},\n"
            f"  版本: {self.version},\n"
            f"  目标语言: {self.target_language},\n"
            f"  跳过: {'是' if self.skip else '否'},\n"
            f"  字段配置:\n    {columns_str}\n"
            f")"
        )
    def __post_init__(self):
        # Excel文件路径
        excel_file_path = f'assets/config/{self.table_name}.xlsx'
        # 如果文件存在则读取数据
        if os.path.exists(excel_file_path):
            self.columns = read_column_config_from_excel(excel_file_path)

# 从 Excel 文件中读取字段配置信息并返回 ColumnConfig 列表
def read_column_config_from_excel(excel_file_path: str) -> List[ColumnConfig]:
    """
    从 Excel 文件中读取字段配置信息，并返回 ColumnConfig 列表。

    :param excel_file_path: Excel 文件的路径
    :return: ColumnConfig 列表
    """
    # 读取 Excel 文件到 DataFrame 中
    df = pd.read_excel(excel_file_path)

    # 检查是否包含必要的列
    required_columns = {'字段中文名', '字段名称', '是否可为空', '是否显示', "是否可编辑", "字段类型", "模拟规则", "值映射关系", "字段备注"}
    if not required_columns.issubset(df.columns):
        raise ValueError(f"Excel 文件缺少必需的列: {required_columns}")

    # 将 DataFrame 转换为 ColumnConfig 实例列表
    column_config_list = []

    for _, row in df.iterrows():
        # 提取字段类型并清理字符串
        field_type_raw = str(row['字段类型']).strip().lower() if pd.notna(row['字段类型']) else "text"

        # 使用 FieldType 枚举的 from_str 方法进行类型转换
        field_type = FieldType.from_str(field_type_raw)

        # 创建 ColumnConfig 实例
        column_config = ColumnConfig(
            name=row['字段名称'],  # 字段名称
            chinese_name=row['字段中文名'],
            column_show=row['是否显示'] in ['True', 'true', 'TRUE', True],  # 确保布尔值
            nullable=row['是否可为空'] in ['True', 'true', 'TRUE', True],  # 确保布尔值
            editable=row['是否可编辑'] in ['True', 'true', 'TRUE', True],  # 确保布尔值
            comment=row['字段备注'],
            value_map=parse_value_map(row['值映射关系']) if pd.notna(row['值映射关系']) else None,  # 解析值映射关系
            field_type=field_type,  # 使用枚举进行类型判断
            mock_rule=row['模拟规则'] if pd.notna(row['模拟规则']) else ''
        )
        # 添加到列表中
        column_config_list.append(column_config)

    return column_config_list

# 将 ColumnConfig 列表转换为 Excel 文件
def save_column_config_to_excel(column_config_list: List[ColumnConfig], excel_file_path: str):
    """
    将 ColumnConfig 列表保存到 Excel 文件中。

    :param column_config_list: ColumnConfig 列表
    :param excel_file_path: Excel 文件的路径
    """
    # 创建一个新的 Excel 工作簿
    wb = Workbook()
    ws = wb.active
    ws.title = "ColumnConfig"

    # 写入表头
    ws.append(["字段中文名", "字段名称", "是否显示", "是否可为空", "是否可编辑", "字段类型", "模拟规则", "值映射关系", "字段备注"])

    # 写入数据
    for config in column_config_list:
        value_map_str = ';'.join([f"{key}:{value}" for key, value in (config.value_map or {}).items()])
        ws.append([
            config.chinese_name,                  # 字段中文名
            config.name,                          # 字段名称
            "True" if config.column_show else "False", # 是否显示
            "True" if config.nullable else "False",    # 是否可为空
            "True" if config.editable else "False",  # 是否可编辑
            config.field_type,                    # 字段类型
            config.mock_rule,                     # 模拟规则
            value_map_str,                        # 值映射关系
            config.comment,                       # 字段备注
        ])
    # 创建数据验证对象，只允许输入 True 或 False
    dv = DataValidation(type="list", formula1='"True,False"', showDropDown=True)
    ws.add_data_validation(dv)

    # 创建数据验证对象，只允许输入 FieldType 的值
    field_type_dv = DataValidation(type="list", formula1='"' + ','.join([ft.value for ft in FieldType]) + '"', showDropDown=True)
    ws.add_data_validation(field_type_dv)


    # 将数据验证应用到特定列的所有单元格
    for row in range(2, len(column_config_list) + 2):
        dv.add(ws[f'C{row}'])
        dv.add(ws[f'D{row}'])
        dv.add(ws[f'H{row}'])  # 允许可编辑状态
        field_type_dv.add(ws[f'E{row}'])

    # 保存 Excel 文件
    wb.save(excel_file_path)

def parse_value_map(value_map_str: str) -> Dict[int, str]:
    """
    将单元格中的 value_map 字符串解析为字典。
    格式示例："1:超级管理员;2:普通用户;3:拓展员;11:店长;12:店员;0:未知类型"
    兼容整数和字符串作为key的情况。
    """
    value_map = {}
    if value_map_str:
        mappings = value_map_str.split(';')
        for mapping in mappings:
            key, value = mapping.split(':')
            # 尝试将 key 转换为 int，如果失败则保留字符串
            try:
                key = int(key)
            except ValueError:
                pass
            value_map[key] = value
    return value_map